using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Net8.Identity.Server.Services;
using static OpenIddict.Abstractions.OpenIddictConstants;

namespace Net8.Identity.Server.Extensions
{
    /// <summary>
    /// 提供OpenIddict和数据库迁移相关的扩展方法
    /// </summary>
    public static class ProgramExtension
    {
        /// <summary>
        /// 配置并注册OpenIddict服务，设置认证服务器
        /// </summary>
        /// <param name="services">服务容器</param>
        /// <param name="configuration">应用程序配置</param>
        /// <param name="environment">托管环境</param>
        /// <returns>配置后的服务容器</returns>
        /// <exception cref="InvalidOperationException">当生产环境证书配置缺失时抛出</exception>
        public static IServiceCollection AddOpenIddictStartup(this IServiceCollection services, IConfiguration configuration, 
            IWebHostEnvironment environment)
        {
            // 从配置中获取数据库连接字符串和加密密钥
            var dbConnectionString = configuration.GetConnectionString("DefaultConnection") ?? "Data Source=Dbfile/IdentityServer.db";
            var encryptionKey = configuration["Jwt:EncryptionKey"] ?? "Qwl6q6Xj0Cf0S09jM43mQ5r3L2dH6vF4N8sP7eR3tE0=";

            // 配置数据库上下文，支持SQLite和MySQL
            services.AddDbContext<ApplicationDbContext>(options =>
            {
                // 根据连接字符串判断数据库类型
                if(!dbConnectionString.Contains("3306"))
                    options.UseSqlite(dbConnectionString);
                else
                    options.UseMySQL(dbConnectionString);
                
                options.UseOpenIddict();
            });

            // 配置OpenIddict服务
            services.AddOpenIddict()
                // 配置核心服务，使用Entity Framework Core进行数据存储
                .AddCore(options =>
                {
                    options.UseEntityFrameworkCore()
                            .UseDbContext<ApplicationDbContext>();
                })
                // 配置服务器端点
                .AddServer(options =>
                {
                    #region OAuth流程配置
                    // 配置支持的 OAuth 2.0/OIDC 流程
                    options.AllowAuthorizationCodeFlow();
                    // 只包含必要的声明
                    options.DisableScopeValidation();
                    options.AcceptAnonymousClients();
                    
                    options.RegisterScopes(Scopes.OpenId, Scopes.Email, Scopes.Roles); 
                    #endregion
                
                // 2. 核心配置：根据环境选择证书
                #region HTTPS证书配置
                //if (environment.IsDevelopment())
                if(true)
                {
                    #region 开发环境配置
                    options.AddEncryptionKey(new SymmetricSecurityKey(
                        Convert.FromBase64String(encryptionKey)));
                    
                    Console.WriteLine("配置 OpenIddict 为开发模式：使用开发证书，禁用 HTTPS。");
                    // 使用 OpenIddict 自动生成的开发证书进行签名和加密
                    options.AddDevelopmentEncryptionCertificate()
                        .AddDevelopmentSigningCertificate();
                    #endregion
                }
                else
                {
                    #region 生产环境配置
                    Console.WriteLine("配置 OpenIddict 为生产模式：使用正式证书，强制 HTTPS。");

                    // 从配置中获取证书路径和密码
                    var certPath = configuration["CertificateSettings:Production:Path"];
                    var certPassword = configuration["CertificateSettings:Production:Password"];

                    // 验证证书配置完整性
                    if (string.IsNullOrEmpty(certPath) || string.IsNullOrEmpty(certPassword))
                    {
                        throw new InvalidOperationException(
                            "生产环境证书配置缺失！请在 appsettings.json 的 'CertificateSettings:Production' 节点配置 'Path' 和 'Password'。");
                    }

                    // 构建完整证书路径并验证文件存在
                    var fullCertPath = Path.Combine(environment.ContentRootPath, certPath);
                    if (!File.Exists(fullCertPath))
                    {
                        throw new FileNotFoundException($"在路径 '{fullCertPath}' 未找到生产环境证书文件。");
                    }

                    // 加载证书并配置签名和加密
                    var certificate = new X509Certificate2(fullCertPath, certPassword);
                    options.AddSigningCertificate(certificate);
                    options.AddEncryptionCertificate(certificate);
                    #endregion
                }

                #endregion
                
                // 3. 配置端点和令牌
                #region 端点配置
                // 设置访问令牌有效期为6小时
                options.SetAccessTokenLifetime(TimeSpan.FromHours(6));
                // 配置授权端点，用于用户授权
                options.SetAuthorizationEndpointUris("oauth/authorize")
                    // 配置会话结束端点，用于用户注销
                    .SetEndSessionEndpointUris("oauth/logout")
                    .SetTokenEndpointUris("oauth/token") // 令牌端点，用于获取令牌
                    .SetUserInfoEndpointUris("oauth/userinfo"); // 用户信息端点，用于获取用户信息
                
                // 4. 配置与 ASP.NET Core 的集成 (推荐配置)
                var openIddictServerAspNetCoreBuilder = options.UseAspNetCore()
                    .EnableAuthorizationEndpointPassthrough() // 需要自定义登录/同意页
                    .EnableEndSessionEndpointPassthrough()   // 需要自定义注销页
                    .EnableTokenEndpointPassthrough() 
                    .EnableUserInfoEndpointPassthrough()
                    .EnableStatusCodePagesIntegration(); 
                #endregion
                
            });

            // 登录认证
            services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(options =>
                {
                    options.LoginPath = "/Authenticate";
                });
            
            return services;
        }

        /// <summary>
        /// 执行数据库迁移，确保数据库架构与模型同步
        /// </summary>
        /// <param name="app">Web应用程序实例</param>
        /// <returns>表示异步操作的任务</returns>
        /// <remarks>
        /// 此方法会在应用启动时自动执行，检查并应用所有待处理的数据库迁移。
        /// 如果迁移失败，会记录错误但不会阻止应用启动（注释中的throw可取消注释以阻止启动）。
        /// </remarks>
        public static async Task UseMigrateDatabaseAsync(this WebApplication app)
        {
            #region 数据库迁移执行
            // 创建服务作用域以获取所需服务
            using var scope = app.Services.CreateScope();
            var services = scope.ServiceProvider;

            // 获取日志记录器
            var logger = services.GetRequiredService<ILoggerFactory>()
                .CreateLogger("ProgramExtension.DatabaseMigrator");

            try
            {
                // 获取迁移服务并执行迁移
                var migrator = services.GetRequiredService<IClientsSeeder>();
                await migrator.EnsureMigrationAsync();
                logger.LogInformation("数据库迁移成功完成。");
            }
            catch (Exception ex)
            {
                // 记录迁移错误
                logger.LogError(ex, "数据库迁移过程中发生错误。");
                // 在某些情况下，你可能希望在这里抛出异常以阻止应用启动
                // throw; 
            }
            #endregion
        }
        
        /// <summary>
        /// 配置CORS策略，从数据库获取配置
        /// </summary>
        /// <param name="app"></param>
        public static async Task UseCorsFromDatabaseAsync(this WebApplication app)
        { 
            using var scope = app.Services.CreateScope();
            var services = scope.ServiceProvider;
            var config = services.GetRequiredService<IConfiguration>();
            var corsSettingsService = services.GetRequiredService<CorsSettingsService>();
            var corsSettings = await corsSettingsService.GetCorsSettingsAsync();
            
            // 应用CORS策略
            const string corsPolicyName = "AllowSpecificOrigins";
            app.UseCors(policy =>
            {
                var setting = corsSettings.FirstOrDefault(s => s.PolicyName == corsPolicyName);
                if (setting != null)
                {
                    // 1. 验证和清理 Origin 字符串
                    var origins = setting.AllowedOrigins
                        .Split(',', StringSplitOptions.RemoveEmptyEntries)
                        .Select(o => o.Trim())
                        .Where(o => Uri.TryCreate(o, UriKind.Absolute, out _)) // 确保是有效的URI
                        .ToArray();
                    
                    if (!origins.Any())
                    {
                        // 如果没有有效的 Origin，则应用默认策略或跳过
                        // policy.WithOrigins("https://localhost:7164"); // 示例
                        return;
                    }
                    
                    // 2. 安全地应用策略
                    policy.WithOrigins(origins);
            
                    if (setting.AllowCredentials)
                    {
                        // 3. 安全检查：如果允许凭据，则 Origin 不能为 *
                        if (origins.Contains("*"))
                        {
                            throw new InvalidOperationException("CORS安全策略：当 'AllowCredentials' 为 true 时，不能将 '*' 设置为允许的源。");
                        }
                        policy.AllowCredentials();
                    }
            
                    if (setting.AllowAnyHeader) policy.AllowAnyHeader();
                    if (setting.AllowAnyMethod) policy.AllowAnyMethod();
                }
                else
                {
                    // 从配置中读取默认值，而不是硬编码
                    var defaultOrigins = config["CorsDefaults:AllowedOrigins"]?
                        .Split(',', StringSplitOptions.RemoveEmptyEntries)
                        .Select(o => o.Trim())
                        .Where(o => Uri.TryCreate(o, UriKind.Absolute, out _))
                        .ToArray() ?? Array.Empty<string>();

                    if (defaultOrigins.Length > 0)
                    {
                        policy.WithOrigins(defaultOrigins);
                    }
            
                    if (bool.TryParse(config["CorsDefaults:AllowAnyHeader"], out var allowAnyHeader) && allowAnyHeader)
                        policy.AllowAnyHeader();
            
                    if (bool.TryParse(config["CorsDefaults:AllowAnyMethod"], out var allowAnyMethod) && allowAnyMethod)
                        policy.AllowAnyMethod();

                    if (bool.TryParse(config["CorsDefaults:AllowCredentials"], out var allowCredentials) && allowCredentials)
                    {
                        if (defaultOrigins.Contains("*"))
                            throw new InvalidOperationException("默认CORS策略：当 'AllowCredentials' 为 true 时，不能将 '*' 设置为允许的源。");

                        policy.AllowCredentials();
                    }
                }
            });
        }
    }
}